package com.cardone.generator;

import com.cardone.generator.mapper.*;
import com.cardone.generator.template.*;
import com.google.common.base.*;
import com.google.common.collect.*;
import lombok.*;
import lombok.experimental.*;
import lombok.extern.slf4j.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.*;
import org.springframework.jdbc.core.*;
import org.springframework.jdbc.support.*;
import org.springframework.util.*;

import java.sql.*;
import java.util.*;

/**
 * PO对象映射工厂
 *
 * @author yaohaitao
 */
@Getter
@Setter
@Accessors(chain = true)
@Slf4j
public class PoMapperFactory {
    private String defaultFieldType;

    private JdbcTemplate jdbcTemplate;

    private String productName;

    private RunTemplate runTemplate;

    private String schemaPattern;

    private Map<Integer, String> typeMap;

    /**
     * 初始化
     */
    public void init() {
        Assert.notNull(this.defaultFieldType);

        Assert.notEmpty(this.typeMap);

        Assert.notNull(this.runTemplate);
    }

    /**
     * 执行
     *
     * @throws Exception 异常
     */
    public void run() throws Exception {
        JdbcUtils.extractDatabaseMetaData(this.jdbcTemplate.getDataSource(), dbmd -> {
            PoMapperFactory.this.initPoMapperMap(dbmd);

            return null;
        });

        this.runTemplate.run();
    }

    /**
     * 初始化实体属性
     *
     * @param dbmd DatabaseMetaData
     * @throws MetaDataAccessException 异常
     * @throws SQLException            异常
     */
    private void initPoMapperMap(final DatabaseMetaData dbmd) throws MetaDataAccessException, SQLException {
        final List<PoMapper> poMapperList = this.runTemplate.findListPoMapper();

        if (CollectionUtils.isEmpty(poMapperList)) {
            return;
        }

        for (final PoMapper poMapper : poMapperList) {
            final String code = poMapper.getCode();

            PoMapper dbPoMapper = null;

            try {
                dbPoMapper = this.findPoMapper(dbmd, poMapper.getTableName());
            } catch (final Exception e) {
                PoMapperFactory.log.error(e.getMessage(), e);
            }

            if (dbPoMapper == null) {
                continue;
            }

            BeanUtils.copyProperties(dbPoMapper, poMapper);

            poMapper.setCode(code);
        }
    }

    /**
     * 查询实体
     *
     * @param dbmd      DatabaseMetaData
     * @param tableName 表名
     * @return 实体
     * @throws Exception 异常
     */
    private PoMapper findPoMapper(final DatabaseMetaData dbmd, final String tableName) throws Exception {
        PoMapper poMapper = null;

        @Cleanup
        final ResultSet rs = dbmd.getTables(null, this.schemaPattern, tableName, null);

        final RowMapperResultSetExtractor<PoMapper> rowMapperResultSetExtractor = new RowMapperResultSetExtractor<PoMapper>(BeanPropertyRowMapper.newInstance(PoMapper.class), 0);

        final List<PoMapper> poMapperList = rowMapperResultSetExtractor.extractData(rs);

        Assert.notEmpty(poMapperList, "没有对应的表：" + tableName);

        poMapper = poMapperList.get(0);

        final String message = "载入:" + tableName;

        PoMapperFactory.log.info(message);

        this.initPoMapperRemarks(tableName, poMapper);

        this.initPoMapperFields(dbmd, tableName, poMapper);

        return poMapper;
    }

    /**
     * 初始化实体说明
     *
     * @param tableName 表名
     * @param poMapper  实体对象
     * @throws MetaDataAccessException 异常
     */
    private void initPoMapperRemarks(final String tableName, final PoMapper poMapper) throws MetaDataAccessException {
        this.productName = JdbcUtils.extractDatabaseMetaData(this.jdbcTemplate.getDataSource(), "getDatabaseProductName").toString();

        this.productName = JdbcUtils.commonDatabaseName(this.productName);

        if ("Oracle".equals(this.productName)) {
            final String sql = "SELECT NVL(COMMENTS, TABLE_NAME) FROM USER_TAB_COMMENTS WHERE TABLE_NAME= ?";

            try {
                final String remarks = this.jdbcTemplate.queryForObject(sql, new Object[]{tableName}, String.class);

                poMapper.setRemarks(remarks);
            } catch (final Exception e) {
                PoMapperFactory.log.error(e.getMessage(), e);
            }
        } else if ("MySQL".equals(this.productName)) {
            final String sql = "select IF(T.TABLE_COMMENT = '' OR T.TABLE_COMMENT IS NULL, T.table_name, T.TABLE_COMMENT) from INFORMATION_SCHEMA.tables t where t.table_schema = ? and t.table_name = ?";

            try {
                final String remarks = this.jdbcTemplate.queryForObject(sql, new Object[]{this.schemaPattern, tableName}, String.class);

                poMapper.setRemarks(remarks);
            } catch (final Exception e) {
                PoMapperFactory.log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * 初始化实体字段
     *
     * @param dbmd      DatabaseMetaData
     * @param tableName 表名
     * @param poMapper  实体对象
     * @throws SQLException 异常
     */
    private void initPoMapperFields(final DatabaseMetaData dbmd, final String tableName, final PoMapper poMapper) throws SQLException {
        Map<String, FieldMapper> fieldMapperMap = Maps.newTreeMap();

        List<FieldMapper> fieldMapperList;

        @Cleanup
        final ResultSet rs = dbmd.getColumns(null, this.schemaPattern, tableName, null);

        final RowMapperResultSetExtractor<FieldMapper> rowMapperResultSetExtractor = new RowMapperResultSetExtractor<FieldMapper>(BeanPropertyRowMapper.newInstance(FieldMapper.class), 0);

        fieldMapperList = rowMapperResultSetExtractor.extractData(rs);

        for (final FieldMapper fieldMapper : fieldMapperList) {
            final String code = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, fieldMapper.getColumnName());

            final String namePascalCase = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, fieldMapper.getColumnName());

            fieldMapper.setCode(code);
            fieldMapper.setName(code);
            fieldMapper.setNamePascalCase(namePascalCase);

            if (fieldMapperMap == null) {
                fieldMapperMap = Maps.newTreeMap();
            }

            String typeCode = this.typeMap.get(fieldMapper.getDataType());

            typeCode = StringUtils.defaultIfBlank(typeCode, this.defaultFieldType);

            fieldMapper.setTypeCode(typeCode);

            fieldMapperMap.put(code, fieldMapper);
        }

        if ("Oracle".equals(this.productName)) {
            final String sql = "SELECT COMMENTS FROM USER_COL_COMMENTS WHERE TABLE_NAME= ? AND COLUMN_NAME = ?";

            for (final FieldMapper fieldMapper : fieldMapperMap.values()) {
                final String remarks = this.jdbcTemplate.queryForObject(sql, new Object[]{tableName, fieldMapper.getColumnName()}, String.class);

                fieldMapper.setRemarks(remarks);
            }
        } else if ("MySQL".equals(this.productName)) {
            final String sql = "SELECT IF(T.COLUMN_COMMENT = '' OR T.COLUMN_COMMENT IS NULL, T.COLUMN_NAME, T.COLUMN_COMMENT) FROM INFORMATION_SCHEMA.COLUMNS T WHERE T.TABLE_SCHEMA = ? AND T.TABLE_NAME = ? AND T.COLUMN_NAME = ?";

            for (final FieldMapper fieldMapper : fieldMapperMap.values()) {
                final String remarks = this.jdbcTemplate.queryForObject(sql, new Object[]{this.schemaPattern, tableName, fieldMapper.getColumnName()}, String.class);

                fieldMapper.setRemarks(remarks);
            }
        }

        poMapper.setFieldMapperMap(fieldMapperMap);

        this.initPoMapperPrimaryKeys(dbmd, tableName, fieldMapperMap);
    }

    /**
     * 初始化实体主键
     *
     * @param dbmd           DatabaseMetaData
     * @param tableName      表名
     * @param fieldMapperMap 字段映射
     * @throws SQLException 异常
     */
    private void initPoMapperPrimaryKeys(final DatabaseMetaData dbmd, final String tableName, final Map<String, FieldMapper> fieldMapperMap) throws SQLException {
        List<FieldMapper> fieldMapperList;

        @Cleanup
        final ResultSet rs = dbmd.getPrimaryKeys(null, this.schemaPattern, tableName);

        final RowMapperResultSetExtractor<FieldMapper> rowMapperResultSetExtractor = new RowMapperResultSetExtractor<FieldMapper>(BeanPropertyRowMapper.newInstance(FieldMapper.class), 0);

        fieldMapperList = rowMapperResultSetExtractor.extractData(rs);

        if (CollectionUtils.isEmpty(fieldMapperList)) {
            return;
        }

        for (final FieldMapper itemFieldMapper : fieldMapperList) {
            final String code = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, itemFieldMapper.getColumnName());

            final FieldMapper fieldMapper = fieldMapperMap.get(code);

            fieldMapper.setKeySeq(itemFieldMapper.getKeySeq());
            fieldMapper.setPkName(itemFieldMapper.getPkName());
        }
    }
}
